<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Plugins\ECRecover\CryptoCurrency;

use Exception;

class Signature
{
    /**
     * @param $R
     * @param $S
     * @param $hash
     * @param $recoveryFlags
     * @return array
     * @throws Exception
     * recoverPublicKey
     */
	public static function recoverPublicKey($R, $S, $hash, $recoveryFlags):array
    {
		$secp256k1      = new SECp256k1();
		$a              = $secp256k1->a;
		$b              = $secp256k1->b;
		$G              = $secp256k1->G;
		$n              = $secp256k1->n;
		$p              = $secp256k1->p;
		$isYEven        = ($recoveryFlags & 1) !== 0;
		$isSecondKey    = ($recoveryFlags & 2) !== 0;
		$e              = gmp_strval($hash, 16);
		$s              = gmp_strval($S, 16);
        $p_over_four    = gmp_div(gmp_add($p, 1), 4);
		if(!$isSecondKey){
			$x  = $R;
		}else{
			$x  = gmp_add($R, $n);
		}
		$alpha  = gmp_mod(gmp_add(gmp_add(gmp_pow($x, 3), gmp_mul($a, $x)), $b), $p);
		$beta   = gmp_strval(gmp_powm($alpha, $p_over_four, $p));
		if(PointMathGMP::isEvenNumber($beta) === $isYEven){
			$y  = gmp_sub($p, $beta);
		}else{
			$y  = gmp_init($beta);
		}
		$Rpt            = ['x' => $x, 'y' => $y];
		$rInv           = gmp_strval(gmp_invert($R, $n), 16);
		$eGNeg          = PointMathGMP::negatePoint(PointMathGMP::mulPoint($e, $G, $a, $b, $p));
		$sR             = PointMathGMP::mulPoint($s, $Rpt, $a, $b, $p);
		$sR_plus_eGNeg  = PointMathGMP::addPoints($sR, $eGNeg, $a, $p);
		$Q              = PointMathGMP::mulPoint($rInv, $sR_plus_eGNeg, $a, $b, $p);
        $pubKey['x']    = str_pad(gmp_strval($Q['x'], 16), 64, '0', STR_PAD_LEFT);
        $pubKey['y']    = str_pad(gmp_strval($Q['y'], 16), 64, '0', STR_PAD_LEFT);
		return $pubKey;
	}
}
